<template>
    <div
            class="el-slider__button-wrapper"
            @mouseenter="handleMouseEnter"
            @mouseleave="handleMouseLeave"
            @mousedown="onButtonDown"
            @touchstart="onButtonDown"
            :class="{ 'hover': hovering, 'dragging': dragging }"
            :style="wrapperStyle"
            ref="button"
            tabindex="0"
            @focus="handleMouseEnter"
            @blur="handleMouseLeave"
            @keydown.left="onLeftKeyDown"
            @keydown.right="onRightKeyDown"
            @keydown.down.prevent="onLeftKeyDown"
            @keydown.up.prevent="onRightKeyDown"
    >
        <el-tooltip
                placement="top"
                ref="tooltip"
                :popper-class="tooltipClass"
                :disabled="!showTooltip">
            <span slot="content">{{ formatValue }}</span>
            <div class="el-slider__button" :class="{ 'hover': hovering, 'dragging': dragging }"></div>
        </el-tooltip>
    </div>
</template>

<script>
    import ElTooltip from 'element-ui/packages/tooltip';

    export default {
        name: 'ElSliderButton',

        components: {
            ElTooltip
        },

        props: {
            value: {
                type: Number,
                default: 0
            },
            vertical: {
                type: Boolean,
                default: false
            },
            tooltipClass: String
        },

        data() {
            return {
                hovering: false,
                dragging: false,
                isClick: false,
                startX: 0,
                currentX: 0,
                startY: 0,
                currentY: 0,
                startPosition: 0,
                newPosition: null,
                oldValue: this.value
            };
        },

        computed: {
            disabled() {
                return this.$parent.sliderDisabled;
            },

            max() {
                return this.$parent.max;
            },

            min() {
                return this.$parent.min;
            },

            step() {
                return this.$parent.step;
            },

            showTooltip() {
                return this.$parent.showTooltip;
            },

            precision() {
                return this.$parent.precision;
            },

            currentPosition() {
                return `${ (this.value - this.min) / (this.max - this.min) * 100 }%`;
            },

            enableFormat() {
                return this.$parent.formatTooltip instanceof Function;
            },

            formatValue() {
                return this.enableFormat && this.$parent.formatTooltip(this.value) || this.value;
            },

            wrapperStyle() {
                return this.vertical ? {bottom: this.currentPosition} : {left: this.currentPosition};
            }
        },

        watch: {
            dragging(val) {
                this.$parent.dragging = val;
            }
        },

        methods: {
            displayTooltip() {
                this.$refs.tooltip && (this.$refs.tooltip.showPopper = true);
            },

            hideTooltip() {
                this.$refs.tooltip && (this.$refs.tooltip.showPopper = false);
            },

            handleMouseEnter() {
                this.hovering = true;
                this.displayTooltip();
            },

            handleMouseLeave() {
                this.hovering = false;
                this.hideTooltip();
            },

            onButtonDown(event) {
                if (this.disabled) return;
                event.preventDefault();
                this.onDragStart(event);
                window.addEventListener('mousemove', this.onDragging);
                window.addEventListener('touchmove', this.onDragging);
                window.addEventListener('mouseup', this.onDragEnd);
                window.addEventListener('touchend', this.onDragEnd);
                window.addEventListener('contextmenu', this.onDragEnd);
            },
            onLeftKeyDown() {
                if (this.disabled) return;
                this.newPosition = parseFloat(this.currentPosition) - this.step / (this.max - this.min) * 100;
                this.setPosition(this.newPosition);
                this.$parent.emitChange();
            },
            onRightKeyDown() {
                if (this.disabled) return;
                this.newPosition = parseFloat(this.currentPosition) + this.step / (this.max - this.min) * 100;
                this.setPosition(this.newPosition);
                this.$parent.emitChange();
            },
            onDragStart(event) {
                this.dragging = true;
                this.isClick = true;
                if (event.type === 'touchstart') {
                    event.clientY = event.touches[0].clientY;
                    event.clientX = event.touches[0].clientX;
                }
                if (this.vertical) {
                    this.startY = event.clientY;
                } else {
                    this.startX = event.clientX;
                }
                this.startPosition = parseFloat(this.currentPosition);
                this.newPosition = this.startPosition;
            },

            onDragging(event) {
                if (this.dragging) {
                    this.isClick = false;
                    this.displayTooltip();
                    this.$parent.resetSize();
                    let diff = 0;
                    if (event.type === 'touchmove') {
                        event.clientY = event.touches[0].clientY;
                        event.clientX = event.touches[0].clientX;
                    }
                    if (this.vertical) {
                        this.currentY = event.clientY;
                        diff = (this.startY - this.currentY) / this.$parent.sliderSize * 100;
                    } else {
                        this.currentX = event.clientX;
                        diff = (this.currentX - this.startX) / this.$parent.sliderSize * 100;
                    }
                    this.newPosition = this.startPosition + diff;
                    this.setPosition(this.newPosition);
                }
            },

            onDragEnd() {
                if (this.dragging) {
                    /*
                     * 防止在 mouseup 后立即触发 click，导致滑块有几率产生一小段位移
                     * 不使用 preventDefault 是因为 mouseup 和 click 没有注册在同一个 DOM 上
                     */
                    setTimeout(() => {
                        this.dragging = false;
                        this.hideTooltip();
                        if (!this.isClick) {
                            this.setPosition(this.newPosition);
                            this.$parent.emitChange();
                        }
                    }, 0);
                    window.removeEventListener('mousemove', this.onDragging);
                    window.removeEventListener('touchmove', this.onDragging);
                    window.removeEventListener('mouseup', this.onDragEnd);
                    window.removeEventListener('touchend', this.onDragEnd);
                    window.removeEventListener('contextmenu', this.onDragEnd);
                }
            },

            setPosition(newPosition) {
                if (newPosition === null || isNaN(newPosition)) return;
                if (newPosition < 0) {
                    newPosition = 0;
                } else if (newPosition > 100) {
                    newPosition = 100;
                }
                const lengthPerStep = 100 / ((this.max - this.min) / this.step);
                const steps = Math.round(newPosition / lengthPerStep);
                let value = steps * lengthPerStep * (this.max - this.min) * 0.01 + this.min;
                value = parseFloat(value.toFixed(this.precision));
                this.$emit('input', value);
                this.$nextTick(() => {
                    this.displayTooltip();
                    this.$refs.tooltip && this.$refs.tooltip.updatePopper();
                });
                if (!this.dragging && this.value !== this.oldValue) {
                    this.oldValue = this.value;
                }
            }
        }
    };
</script>
